/**
 * User: alesj
 * Date: 2004.8.24
 * Time: 10:36:14
 */

package com.generalynx.ecos.web.interceptor;

import com.generalynx.common.data.IVersion;
import com.generalynx.ecos.data.IAttachable;
import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.StaleObjectStateException;
import org.hibernate.criterion.Property;
import org.hibernate.criterion.Restrictions;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Enumeration;


/**
 * Checks if object version differs and marks it dirty.
 * Reattaches objects to current Hibernate session.
 * Moves all http session attributes to request or model map - depending on view type, but equal for all application.
 *
 * After completion it resets reloaded objects.
 */
public class ObjectAttacherInterceptor extends SessionInterceptorAdapter {

    private boolean toModelMap;
    private boolean useSession = true;

    public ObjectAttacherInterceptor() {
    }

    public ObjectAttacherInterceptor(boolean useSession) {
        this.useSession = useSession;
    }

    public boolean isToModelMap() {
        return toModelMap;
    }

    public void setToModelMap(boolean toModelMap) {
        this.toModelMap = toModelMap;
    }

    public boolean isUseSession() {
        return useSession;
    }

    public void setUseSession(boolean useSession) {
        this.useSession = useSession;
    }

    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler,
                           ModelAndView modelAndView) throws Exception {

        HttpSession httpSession = request.getSession();
        Enumeration enums = httpSession.getAttributeNames();
        Session session = SessionFactoryUtils.getSession(getSessionFactory(), false);
        while(enums.hasMoreElements()) {
            String modelName = enums.nextElement().toString();
            Object modelValue = httpSession.getAttribute(modelName);
            if (modelValue != null) {
                if (isUseSession() && modelValue instanceof IVersion) {
                    doVersionCheck((IVersion)modelValue, session);
                }
                if (isUseSession() && modelValue instanceof IAttachable && ((IAttachable)modelValue).doReload()) {
                    modelValue = doAttach((IAttachable)modelValue, session, httpSession, modelName);
                }
                doMoveToModel(modelValue, modelName, modelAndView, request);
            }
        }
    }

    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) throws Exception {
        HttpSession httpSession = request.getSession();
        Enumeration enums = httpSession.getAttributeNames();
        while(enums.hasMoreElements()) {
            String modelName = enums.nextElement().toString();
            Object modelValue = httpSession.getAttribute(modelName);
            if (modelValue != null) {
                if (modelValue instanceof IAttachable) {
                    IAttachable attachable = (IAttachable)modelValue;
                    attachable.setReloaded(false);
                }
            }
        }

    }

    private void doVersionCheck(IVersion versionable, Session session) {
        if (versionable.check()) {
            Integer oldVersion = versionable.getVersion();
            Criteria criteria = session.createCriteria(Hibernate.getClass(versionable))
                    .setProjection(Property.forName("version"))
                    .add(Restrictions.eq("id", versionable.getKey()));
            Integer newVersion = (Integer)criteria.uniqueResult();
            if (!oldVersion.equals(newVersion)) {
                versionable.setDirty(true);
                if (logger.isWarnEnabled()) {
                    logger.warn("Stale object state [" + versionable.getClass() + " (" +
                                versionable.getKey() + ")] : " +
                                newVersion + " != " + oldVersion);
                }
                if (versionable.isStaleObjectState()) {
                    throw new StaleObjectStateException(versionable.getClass().getName(),
                                                        versionable.getKey());
                }
            }
        }
    }

    private Object doAttach(IAttachable attachable,
                          Session session,
                          HttpSession httpSession,
                          String modelName) {
        IAttachable newModelValue = (IAttachable)session.get(Hibernate.getClass(attachable),
                                                             attachable.getKey(),
                                                             attachable.getLockMode());
        if (newModelValue != null) {
            httpSession.setAttribute(modelName, newModelValue);
            newModelValue.setReloaded(true);
            if (logger.isDebugEnabled()) {
                logger.debug("Reattaching object '" + attachable + "' of type [" +
                             attachable.getClass().getName() + "] to " + session);
            }
            return newModelValue;
        } else {
            throw new StaleObjectStateException(attachable.getClass().getName(),
                                                "Object of " + attachable.getClass().getName() + " instance, " +
                                                "with key = " + attachable.getKey() + " doesn't exist!");
        }
    }

    private void doMoveToModel(Object modelValue,
                               String modelName,
                               ModelAndView modelAndView,
                               HttpServletRequest request) {
        if (isToModelMap()) {
            modelAndView.addObject(modelName, modelValue);
        } else {
            request.setAttribute(modelName, modelValue);
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
                "] to request in SessionAttributeInterceptor.");
        }
    }

}
